package se.gala.games.quetzalcoatl;

import java.util.Set;
import java.util.Vector;

import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.util.Duration;
import se.gala.core.M3GMove;
import se.gala.rng.RNG;
import se.gala.utils.Coordinate2D;
import se.gala.utils.GGObject;

public final class UIAnimationFactory extends GGObject {
	
	private static boolean operationInProgress;

	public static boolean isOperationInProgress() {
		return operationInProgress;
	}

	private synchronized static void setOperationInProgress(boolean operationInProgress) {
		UIAnimationFactory.operationInProgress = operationInProgress;
		if (operationInProgress) {
			UIQuetzalcoatl.uiqc.btnReshuffle.setDisable(true);
			UIQuetzalcoatl.uiqc.btnBooster.setDisable(true);
			UIQuetzalcoatl.uiqc.btnRandom.setDisable(true);
			UIQuetzalcoatl.uiqc.btnRandomMove.setDisable(true);
			UIQuetzalcoatl.uiqc.boxBet.setDisable(true);
		} else {
			UIQuetzalcoatl.uiqc.btnReshuffle.setDisable(false);
			UIQuetzalcoatl.uiqc.btnBooster.setDisable(false);
			UIQuetzalcoatl.uiqc.btnRandom.setDisable(false);
			UIQuetzalcoatl.uiqc.btnRandomMove.setDisable(false);
			UIQuetzalcoatl.uiqc.boxBet.setDisable(false);
		}
	}

	protected synchronized static void doStoneCrashBoosterAnimation() {
		setOperationInProgress(true);
		cleanAnimationsInProgress();

		final RNG rng = UIQuetzalcoatl.getGame().getRNG();
		final Vector<GemButton> stoneGems = new Vector<GemButton>();
		
		final Duration d = Duration.millis(600);
		final Timeline t = new Timeline();
		t.setCycleCount(1);
		t.setAutoReverse(false);

		final Set<String> keys = UIQuetzalcoatl.getGems().keySet();
		for (String key : keys) {
			final GemButton gb = UIQuetzalcoatl.getGems().get(key);
			if (gb.getSymbolCurrentlyDisaplayed() != QSymbols.Stone) {
				continue;
			}
			stoneGems.add(gb);
			
			double newX = gb.layoutXProperty().get();
			double newY = gb.layoutYProperty().get();
			newX += rng.getIntegerBetween(2 * UIQuetzalcoatl.GEM_WIDTH,
					4 * UIQuetzalcoatl.GEM_WIDTH) * (rng.getBoolean() ? 1 : -1);
			newY += rng.getIntegerBetween(2 * UIQuetzalcoatl.GEM_HEIGHT,
					4 * UIQuetzalcoatl.GEM_HEIGHT)
					* (rng.getBoolean() ? 1 : -1);
			final KeyValue kv0 = new KeyValue(gb.opacityProperty(), 0);
			final KeyValue kv1 = new KeyValue(gb.translateXProperty(), newX,
					Interpolator.EASE_IN);
			final KeyValue kv2 = new KeyValue(gb.translateYProperty(), newY,
					Interpolator.EASE_IN);

			t.getKeyFrames().addAll(
					new KeyFrame(d, kv0),
					new KeyFrame(d, kv1),
					new KeyFrame(d, kv2));
		}
		
		t.setOnFinished(new EventHandler<ActionEvent>() {
			@Override
			public void handle(ActionEvent arg0) {
				UIQuetzalcoatl.repaintGems();
				final Duration d = Duration.millis(500);
				final Timeline t2 = new Timeline();
				t2.setCycleCount(1);
				t2.setAutoReverse(false);
				/*
				 * Cleaning after the booster animation by reverting
				 * all affected properties from the stone gems
				 * to their default values.
				 */
				for (GemButton gb : stoneGems) {
					gb.scaleXProperty().set(1.3);
					gb.scaleYProperty().set(1.3);
					double newX = gb.layoutXProperty().get();
					double newY = gb.layoutYProperty().get();
					newX += rng.getIntegerBetween(2 * UIQuetzalcoatl.GEM_WIDTH,
							4 * UIQuetzalcoatl.GEM_WIDTH) * (rng.getBoolean() ? 1 : -1);
					newY += rng.getIntegerBetween(2 * UIQuetzalcoatl.GEM_HEIGHT,
							4 * UIQuetzalcoatl.GEM_HEIGHT) * (rng.getBoolean() ? 1 : -1);
					gb.translateXProperty().set(newX);
					gb.translateYProperty().set(newY);
					final KeyValue kv00 = new KeyValue(gb.opacityProperty(), 1);
					final KeyValue kv01 = new KeyValue(gb.scaleXProperty(), 1);
					final KeyValue kv02 = new KeyValue(gb.scaleYProperty(), 1);
					final KeyValue kv1 = new KeyValue(gb.translateXProperty(), 0,
							Interpolator.EASE_OUT);
					final KeyValue kv2 = new KeyValue(gb.translateYProperty(), 0,
							Interpolator.EASE_OUT);
					t2.getKeyFrames().addAll(
							new KeyFrame(d, kv00, kv01, kv02),
							new KeyFrame(d, kv1),
							new KeyFrame(d, kv2));
				}
				t2.setOnFinished(new EventHandler<ActionEvent>() {
					@Override
					public void handle(ActionEvent event) {
						setOperationInProgress(false);
						UIQuetzalcoatl.burnWinnings();
					}});
				t2.play();
			}
		});
		t.play();
	}
	
	protected synchronized static void doShakerAnimation() {
		setOperationInProgress(true);
		cleanAnimationsInProgress();

		final RNG rng = UIQuetzalcoatl.getGame().getRNG();
		
		final Duration d = Duration.millis(600);
		final Timeline t = new Timeline();
		t.setCycleCount(1);
		t.setAutoReverse(false);

		final Set<String> keys = UIQuetzalcoatl.getGems().keySet();
		for (String key : keys) {
			final GemButton gb = UIQuetzalcoatl.getGems().get(key);
			double newX = gb.layoutXProperty().get();
			double newY = gb.layoutYProperty().get();
			newX += rng.getIntegerBetween(2 * UIQuetzalcoatl.GEM_WIDTH,
					4 * UIQuetzalcoatl.GEM_WIDTH) * (rng.getBoolean() ? 1 : -1);
			newY += rng.getIntegerBetween(2 * UIQuetzalcoatl.GEM_HEIGHT,
					4 * UIQuetzalcoatl.GEM_HEIGHT)
					* (rng.getBoolean() ? 1 : -1);
			final KeyValue kv0 = new KeyValue(gb.opacityProperty(), 0);
			final KeyValue kv1 = new KeyValue(gb.translateXProperty(), newX,
					Interpolator.EASE_IN);
			final KeyValue kv2 = new KeyValue(gb.translateYProperty(), newY,
					Interpolator.EASE_IN);

			t.getKeyFrames().addAll(
					new KeyFrame(d, kv0),
					new KeyFrame(d, kv1),
					new KeyFrame(d, kv2));
		}
		t.play();
		
		t.setOnFinished(new EventHandler<ActionEvent>() {
			@Override
			public void handle(ActionEvent arg0) {
				UIQuetzalcoatl.repaintGems();
				final Duration d = Duration.millis(500);
				final Timeline t2 = new Timeline();
				t2.setCycleCount(1);
				t2.setAutoReverse(false);
				/*
				 * Cleaning after the burn animation by reverting
				 * all affected properties to their default value.
				 */
				final Set<String> keys = UIQuetzalcoatl.getGems().keySet();
				for (String key : keys) {
					final GemButton gb = UIQuetzalcoatl.getGems().get(key);
					gb.scaleXProperty().set(1.3);
					gb.scaleYProperty().set(1.3);
					double newX = gb.layoutXProperty().get();
					double newY = gb.layoutYProperty().get();
					newX += rng.getIntegerBetween(2 * UIQuetzalcoatl.GEM_WIDTH,
							4 * UIQuetzalcoatl.GEM_WIDTH) * (rng.getBoolean() ? 1 : -1);
					newY += rng.getIntegerBetween(2 * UIQuetzalcoatl.GEM_HEIGHT,
							4 * UIQuetzalcoatl.GEM_HEIGHT) * (rng.getBoolean() ? 1 : -1);
					gb.translateXProperty().set(newX);
					gb.translateYProperty().set(newY);
					final KeyValue kv00 = new KeyValue(gb.opacityProperty(), 1);
					final KeyValue kv01 = new KeyValue(gb.scaleXProperty(), 1);
					final KeyValue kv02 = new KeyValue(gb.scaleYProperty(), 1);
					final KeyValue kv1 = new KeyValue(gb.translateXProperty(), 0,
							Interpolator.EASE_OUT);
					final KeyValue kv2 = new KeyValue(gb.translateYProperty(), 0,
							Interpolator.EASE_OUT);
					t2.getKeyFrames().addAll(
							new KeyFrame(d, kv00, kv01, kv02),
							new KeyFrame(d, kv1),
							new KeyFrame(d, kv2));
				}
				t2.play();
				t2.setOnFinished(new EventHandler<ActionEvent>() {
					@Override
					public void handle(ActionEvent event) {
						setOperationInProgress(false);
					}});
			}
		});
	}

	protected synchronized static void doInitialAnimation() {
		setOperationInProgress(true);
		UIQuetzalcoatl.repaintGems();
		
		final Timeline[] tlines = new Timeline[GemMatrix.MATRIX_SIZE_X * GemMatrix.MATRIX_SIZE_Y];
		Timeline lastToFinish = null;
		Duration lastToFinishDuration = null;
		
		for (int y = 0; y < GemMatrix.MATRIX_SIZE_Y; y++) {
			for (int x = 0; x < GemMatrix.MATRIX_SIZE_X; x++) {
				final Coordinate2D c = new Coordinate2D(x, y);
				final GemButton gb = UIQuetzalcoatl.getGemByCoordinate(c);
	    		double newY = gb.layoutYProperty().get();
				newY -= (2 * UIQuetzalcoatl.GEM_HEIGHT) + (newY * 10);
				gb.translateYProperty().set(newY);
	
				final Timeline t = new Timeline();
				final KeyValue kv = new KeyValue(gb.translateYProperty(), 0,
						 Interpolator.EASE_OUT);
				t.setCycleCount(1);
				t.setAutoReverse(false);
				t.getKeyFrames().add(new KeyFrame(Duration.millis(1200), kv));
				t.setDelay(new Duration(150 * (GemMatrix.MATRIX_SIZE_Y - (y + 1)) + 50 * x));
				
				final Duration tTotal = t.getTotalDuration().add(t.getDelay());
				tlines[y * GemMatrix.MATRIX_SIZE_Y + x] = t;
				if (lastToFinish == null || tTotal.greaterThan(lastToFinishDuration)) {
					lastToFinish = t;
					lastToFinishDuration = tTotal;
				}
			}
		}
		/* Set on finish event handler for the animation that's finishing last. */
		lastToFinish.setOnFinished(new EventHandler<ActionEvent>() {
			@Override
			public void handle(ActionEvent event) {
				setOperationInProgress(false);
				UIQuetzalcoatl.burnWinnings();
			}
		});
		/* Launch each of the animations. */
		for (Timeline t : tlines) {
			t.play();
		}
	}

	protected synchronized static void doClosingAnimation(boolean shouldInitOnFinished) {
		setOperationInProgress(true);
		cleanAnimationsInProgress();
		
		Timeline lastTimeline = null;
		for (int x = 0; x < GemMatrix.MATRIX_SIZE_X; x++) {
			for (int y = 0; y < GemMatrix.MATRIX_SIZE_Y; y++) {
				final Coordinate2D c = new Coordinate2D(x, y);
				final GemButton gb = UIQuetzalcoatl.getGemByCoordinate(c);
	    		double newY = gb.layoutYProperty().get();
				newY += 400;
				final Timeline t = new Timeline();
				t.setCycleCount(1);
				t.setAutoReverse(false);
				final KeyValue kv = new KeyValue(gb.translateYProperty(), newY,
						 Interpolator.EASE_IN);
				t.getKeyFrames().add(new KeyFrame(Duration.millis(500), kv));
				final Duration duration = new Duration(20 * (GemMatrix.MATRIX_SIZE_Y - (y + 1)) + 150 * x);
				t.setDelay(duration);
				if (x == GemMatrix.MATRIX_SIZE_X - 1 && y == 0) {
					lastTimeline = t;
				}
				t.play();
			}
		}
		if (shouldInitOnFinished) {
			lastTimeline.setOnFinished(new EventHandler<ActionEvent>() {
	            @Override
	            public void handle(ActionEvent arg0) {
	            	doInitialAnimation();
	            }
			});
		}
	}

	protected static void doSwapGemsAnimation(M3GMove mv,
			boolean shouldFinalizeSwap, boolean shouldRepaintGems,
			boolean shouldBurnWinnings) {
		setOperationInProgress(true);
		cleanAnimationsInProgress();
		
		final Obj4Coordinates o = determineSwapPositions(mv.loc1, mv.loc2);

		final GemButton gb1 = UIQuetzalcoatl.getGemByCoordinate(mv.loc1);
		final GemButton gb2 = UIQuetzalcoatl.getGemByCoordinate(mv.loc2);
				
		final int cycleCount = shouldFinalizeSwap ? 1 : 2;
		final double animDuration = shouldFinalizeSwap ? 350 : 700;
		
		final Timeline t = new Timeline();
		t.setCycleCount(cycleCount);
		t.setAutoReverse(!shouldFinalizeSwap);
		t.getKeyFrames().add(
				new KeyFrame(Duration.millis(animDuration), 
						new KeyValue(gb1.translateXProperty(), o.gb1x),
						new KeyValue(gb1.translateYProperty(), o.gb1y),
						new KeyValue(gb2.translateXProperty(), o.gb2x),
						new KeyValue(gb2.translateYProperty(), o.gb2y)
				));
		t.play();
		t.setOnFinished(new EventHandler<ActionEvent>() {
			 
	        @Override
	        public void handle(ActionEvent arg0) {
				if (shouldFinalizeSwap) {
					UIQuetzalcoatl.getGame().getMatrix().swapSymbols(mv);
					if (shouldRepaintGems) {
						UIQuetzalcoatl.repaintGems();
					}
	            	gb1.translateXProperty().set(0);
	            	gb1.translateYProperty().set(0);
	            	gb2.translateXProperty().set(0);
	            	gb2.translateYProperty().set(0);
					if (shouldBurnWinnings) {
						UIQuetzalcoatl.burnWinnings();
					} else {
		            	setOperationInProgress(false);
					}
				}
	        }
		});
	}
	
	/**
	 * Drag over animation, that's being called when the player drags one gem over another.
	 * @param gb1 The button that's being dragged over.
	 * @param gb2 The button that's being dragged.
	 * @return The total duration of the animation in seconds. 
	 */
	protected static synchronized double doGemDraggedOverAnimation(GemButton gb1, GemButton gb2) {
		cleanAnimationsInProgress();
		
		final Timeline timelineDraggedOver = new Timeline();
		timelineDraggedOver.setCycleCount(1);
		timelineDraggedOver.setAutoReverse(false);
		timelineDraggedOver.getKeyFrames().add(
				new KeyFrame(Duration.millis(150), 
						new KeyValue(gb1.scaleXProperty(), 1)
				));
		timelineDraggedOver.play();

		timelineDraggedOver.setOnFinished(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent arg0) {
            	gb1.translateXProperty().set(0);
            	gb1.translateYProperty().set(0);
            	gb1.scaleXProperty().set(1);
            	gb1.scaleYProperty().set(1);
            }
		});
		return timelineDraggedOver.getTotalDuration().toSeconds();
	}

	protected synchronized static void doBurnAnimation(Vector<QuetzalSymbol> burnMatrix) {
    	setOperationInProgress(true);
		cleanAnimationsInProgress();

		final Vector<Timeline> slidingGemsTimelines = new Vector<Timeline>();

		final Timeline fixedTimeline = new Timeline();
		fixedTimeline.setCycleCount(5);
		fixedTimeline.setAutoReverse(true);		
		fixedTimeline.setOnFinished(new EventHandler<ActionEvent>(){
	        @Override
	        public void handle(ActionEvent arg0) {
				/*
				 * Cleaning after the burn animation by reverting
				 * all affected properties to their default value.
				 */
	    		for (QuetzalSymbol s : burnMatrix) {
	    			GemButton gb = UIQuetzalcoatl.getGemByCoordinate(s.getCoordinate());
	    			gb.rotateProperty().set(0);
	    			gb.opacityProperty().set(1);
	    			gb.translateXProperty().set(0);
	    			gb.translateYProperty().set(0);
	    			gb.scaleXProperty().set(1);
	    			gb.scaleYProperty().set(1);
	    		}
            	setOperationInProgress(false);
	    		/* Repaint the gems now to get the new layout */
	        	UIQuetzalcoatl.repaintGems();
        		doGemsDroppingAnimation();
	        }
		});
		
		final Timeline burnTimeline = new Timeline();
		burnTimeline.setCycleCount(1);
		burnTimeline.setAutoReverse(true);
		
		final int durFixedTimeline = 200;
		final int durSlidingGemsTimeline = 250;
		final int durBurnTimeline = 500;
		final int durBurnPreciousTimeline = 1000;
		for (QuetzalSymbol s : burnMatrix) {
			final GemButton gb = UIQuetzalcoatl.getGemByCoordinate(s.getCoordinate());
			gb.setSelected(false);
			gb.setHoverState(false);
			
			switch ((QSymbols) s.getValue()) {
			case Silver:
			case Copper:
			case Gold:
				burnTimeline.getKeyFrames().add(
						new KeyFrame(Duration.millis(durBurnTimeline), 
								new KeyValue(gb.rotateProperty(), 360),
								new KeyValue(gb.opacityProperty(), 0),
								new KeyValue(gb.scaleXProperty(), 0.3),
								new KeyValue(gb.scaleYProperty(), 0.3)
						));
				break;
			case Stone:
				if (s.getFixed()) {
					fixedTimeline.getKeyFrames().add(
							new KeyFrame(Duration.millis(durFixedTimeline), 
									new KeyValue(gb.scaleXProperty(), 1.1),
									new KeyValue(gb.scaleYProperty(), 1.1)
							));
				} else {
					final Obj4Coordinates obj4c = new Obj4Coordinates();
					final int dX = s.getTargetFixedX() - s.getSourceGemIndexX();
					final int dY = s.getTargetFixedY() - s.getSourceGemIndexY();
					if (s.isDirectionX()) {
						obj4c.gb1x = UIQuetzalcoatl.GEM_WIDTH * dX;
					}
					if (s.isDirectionY()) {
						obj4c.gb1y = UIQuetzalcoatl.GEM_HEIGHT * dY;
					}
					final KeyFrame kf = new KeyFrame(Duration.millis(durSlidingGemsTimeline), 
							new KeyValue(gb.translateXProperty(), obj4c.gb1x),
							new KeyValue(gb.translateYProperty(), obj4c.gb1y),
							new KeyValue(gb.opacityProperty(), 0)
					);
					final Timeline t = new Timeline();
					t.setCycleCount(1);
					t.setAutoReverse(false);		
					t.getKeyFrames().add(kf);
					t.setDelay(Duration.millis(durSlidingGemsTimeline
							* (Math.max(Math.abs(dX), Math.abs(dY) - 1))));
					slidingGemsTimelines.add(t);
				}
				break;
			case Ruby:
			case Sapphire:
			case Emerald:
				burnTimeline.getKeyFrames().add(
						new KeyFrame(Duration.millis(durBurnPreciousTimeline),
								new KeyValue(gb.opacityProperty(), 0),
								new KeyValue(gb.scaleXProperty(), 1.3),
								new KeyValue(gb.scaleYProperty(), 1.3)
						));
				break;
			case Jade:
			case Obsidian:
			case Turquoise:
			case NONE:
			default:
				burnTimeline.getKeyFrames().add(
						new KeyFrame(Duration.millis(durBurnTimeline),
								new KeyValue(gb.opacityProperty(), 0),
								new KeyValue(gb.scaleXProperty(), 0.3),
								new KeyValue(gb.scaleYProperty(), 0.3)
						));
				break;
			
			}
		}

		Timeline lastToFinish = burnTimeline;
		Duration lastToFinishDuration = burnTimeline.getTotalDuration().add(burnTimeline.getDelay());
		
		for (Timeline t : slidingGemsTimelines) {
			t.play();
			final Duration tTotal = t.getTotalDuration().add(t.getDelay());
			if (tTotal.greaterThan(lastToFinishDuration)) {
				lastToFinish = t;
				lastToFinishDuration = tTotal;
			}
		}

		lastToFinish.setOnFinished(new EventHandler<ActionEvent>(){
	        @Override
	        public void handle(ActionEvent arg0) {
	        	UIQuetzalcoatl.getGame().burnWinnings();
	    		fixedTimeline.play();
	        }
	    });
		burnTimeline.play();
	}
	
	private static void doGemsDroppingAnimation() {
    	setOperationInProgress(true);
		final Timeline[] columnTimelines = new Timeline[GemMatrix.MATRIX_SIZE_X];
		
		for (int x = 0; x < GemMatrix.MATRIX_SIZE_X; x++) {
			final Timeline dropTimeline = new Timeline();
			dropTimeline.setCycleCount(1);
			dropTimeline.setAutoReverse(false);
			columnTimelines[x] = dropTimeline;
			
			final Double[] gemColumnYPositions = new Double[GemMatrix.MATRIX_SIZE_Y];
			final int[] gemColumnYDeviations = new int[GemMatrix.MATRIX_SIZE_Y];
			final GemButton[] gemColumn = new GemButton[GemMatrix.MATRIX_SIZE_Y];
			int droppedDownGems = 0;
			for (int y = GemMatrix.MATRIX_SIZE_Y - 1; y >= 0; y--) {
				final Coordinate2D currentCoordinate = new Coordinate2D(x, y);
				final GemButton gb = UIQuetzalcoatl.getGemByCoordinate(currentCoordinate);
				final Coordinate2D originalCoordinate = gb.getQuetzalSymbol().getOriginalCoordinate();
				originalCoordinate.y -= droppedDownGems;
				if (originalCoordinate.y < 0) {
					droppedDownGems++;
				}
				final int yDeviation = Math.abs(originalCoordinate.y - currentCoordinate.y);
				gemColumn[y] = gb;
				if (yDeviation == 0) {
					gemColumnYPositions[y] = null;
				} else {
					final double yPosition = -UIQuetzalcoatl.GEM_HEIGHT * yDeviation;
					gemColumnYPositions[y] = yPosition;
				}
				gemColumnYDeviations[y] = yDeviation;
			}
			for (int y = GemMatrix.MATRIX_SIZE_Y - 1; y >= 0; y--) {
				final Double yPosition = gemColumnYPositions[y];
				final int yDeviation = gemColumnYDeviations[y];
				if (yPosition != null) {
					final GemButton gb = gemColumn[y];
					gb.setTranslateY(yPosition);
					dropTimeline.getKeyFrames().add(
							new KeyFrame(Duration.millis(250 * yDeviation),
									new KeyValue(gb.translateYProperty(), 0))
							);
				}
			}
		}
		

		Timeline lastToFinish = columnTimelines[0];
		Duration lastToFinishDuration = lastToFinish.getTotalDuration();
		
		int droppedColumns = 0;
		for (Timeline t : columnTimelines) {
			if (t == null || t.getTotalDuration().greaterThan(Duration.ZERO) == false) {
				continue;
			}
			t.setDelay(Duration.millis(droppedColumns * 200));
			t.play();
			Duration tTotal = t.getTotalDuration().add(t.getDelay());
			if (tTotal.greaterThan(lastToFinishDuration)) {
				lastToFinish = t;
				lastToFinishDuration = tTotal;
			}
			droppedColumns++;
		}
		
		lastToFinish.setOnFinished(new EventHandler<ActionEvent>() {
	        @Override
	        public void handle(ActionEvent arg0) {
	        	if (UIQuetzalcoatl.checkForWinnings()) {
	        		//log("******** AUTOMATIC BURN *********");
	        		UIQuetzalcoatl.burnWinnings();
	        	} else {
	        		if(!UIQuetzalcoatl.getGame().checkForLegalMoves()) {
	        			//log("*********************************");
	        			//log("******** NO LEGAL MOVES *********");
	        			//log("*********************************");
	            		try { 
	            			Thread.sleep(300);
	            		} catch (InterruptedException e) {
	            			e.printStackTrace();
	            		}
	        			UIQuetzalcoatl.doReshuffle();
	        		} else {
		            	setOperationInProgress(false);
	        		}
	        	}
	        }
		});
		
    	if (lastToFinish.getTotalDuration().greaterThan(Duration.ZERO) == false) {
    		/* 
    		 * In case there were no gems to drop, we have to recall 
    		 * the play(), in order to handle the setOnFinished event.
    		 */
    		lastToFinish.play();
    	}

	}
		
	private static synchronized void cleanAnimationsInProgress() {
		GemButton.lastClickedButton = null;
	}

	private static Obj4Coordinates determineSwapPositions(Coordinate2D sourceLocation, Coordinate2D targetLocation) {
		Obj4Coordinates o = new Obj4Coordinates();
		int position = sourceLocation.compareTo(targetLocation);
		
		if (sourceLocation.isSameX(targetLocation)) {
			if (position == Coordinate2D.COMPARED_TL || position == Coordinate2D.COMPARED_TR) {
				o.gb1y = UIQuetzalcoatl.GEM_HEIGHT;
				o.gb2y = -UIQuetzalcoatl.GEM_HEIGHT;
			} else {
				o.gb1y = -UIQuetzalcoatl.GEM_HEIGHT;
				o.gb2y = UIQuetzalcoatl.GEM_HEIGHT;
			}			
		}
		if (sourceLocation.isSameY(targetLocation)) {
			if (position == Coordinate2D.COMPARED_TL || position == Coordinate2D.COMPARED_BL) {
				o.gb1x = UIQuetzalcoatl.GEM_WIDTH;
				o.gb2x = -UIQuetzalcoatl.GEM_WIDTH;
			} else {
				o.gb1x = -UIQuetzalcoatl.GEM_WIDTH;
				o.gb2x = UIQuetzalcoatl.GEM_WIDTH;
			}			
		}
		return o;
	}

}

final class Obj4Coordinates {
	public double gb1x = 0, gb1y = 0, gb2x = 0, gb2y = 0;
	
	public String toString() {
		return "Obj4Coordinates {gb1x:" + gb1x + " gb1y:" + gb1y + " gb2x:" + gb2x + " gb2y:" + gb2y + "}";
	}
}
